package com.monochromebytes.mononet.packet;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;

/**
 * Receives packages from a DatagramChannel and adds them to an Inbox.
 * 
 * In its run() method it receives packets in a while loop. It stops when the
 * Thread has been interrupted.
 * 
 * An occouring IOException closes the whole UDPSocket.
 */
public class UDPSocketReceiver implements Runnable {

    /**
     * The socket that has started this worker and handles the channel.
     */
    private final UDPSocket  socket;

    /**
     * The inbox incoming packets will be added to.
     */
    private final Inbox      inbox;

    /**
     * The buffer incoming data will be stored in temporarily.
     */
    private final ByteBuffer buffer;

    /**
     * Creates a new UDPSocketReceiver that uses the given Inbox for the
     * transfer of packets.
     * 
     * The UDPSocketReceiver is not bound to an UDP port and IP address.
     * 
     * @param inbox
     *            The Inbox received packets will be aded to.
     * @param outbox
     *            The Outbox the UDPSocketWorker will fetch the packets from to
     *            send them.
     */
    public UDPSocketReceiver(UDPSocket socket, Inbox inbox) {
        this.socket = socket;
        this.inbox = inbox;
        this.buffer = ByteBuffer.allocate(Packet.MAX_LENGTH);
    }

    /*
     * (non-Javadoc)
     * 
     * @see java.lang.Runnable#run()
     */
    @Override
    public synchronized void run() {
        while (!Thread.interrupted()) {
            try {
                DatagramChannel channel = this.socket.getChannel();

                // Fetch an incoming packet if one has arrived.
                InetSocketAddress sender = (InetSocketAddress) channel.receive(this.buffer);

                // Check if a packet has been received.
                if (sender != null) {
                    sender = new InetSocketAddress(sender.getAddress(), sender.getPort());

                    // Mark the incoming data as the remaining data of the
                    // buffer.
                    this.buffer.limit(this.buffer.position());
                    this.buffer.position(0);

                    // Create a new packet and transfer it to the Inbox.
                    IncomingPacket packet = new IncomingPacket(this.buffer.remaining(), sender,
                            this.socket.getAddress());
                    packet.putData(this.buffer);
                    this.inbox.receive(packet);

                    // Clear the buffer. Its position is set to 0 and its limit
                    // is set to its capacity.
                    this.buffer.clear();
                }
            } catch (IOException e) {
                try {
                    // Close the socket if any I/O Exception has been thrown.
                    this.socket.close(e);
                } catch (IOException eClose) {
                    eClose.printStackTrace();
                }
            }
        }
    }
}
